from datetime import timedelta, datetime

from flask import Flask, render_template, request, redirect, url_for, session, flash, jsonify
import os
import warnings
import matplotlib

matplotlib.use('Agg')
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np
from wordcloud import WordCloud
import jieba
import io
import base64
import logging
from spark_processor import SparkProcessor
import config

# 配置matplotlib中文字体
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans', 'Arial Unicode MS', 'Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False

# 忽略所有警告
warnings.filterwarnings("ignore")
logging.getLogger('matplotlib').setLevel(logging.ERROR)

# 创建Flask应用实例
app = Flask(__name__)
app.config.from_object(config)

# 导入模型并初始化扩展
from models import db, bcrypt, create_tables, User

# 初始化扩展
db.init_app(app)
bcrypt.init_app(app)
# 图表缓存
_chart_cache = {}
_cache_timeout = timedelta(minutes=5)


def get_cached_chart(key, generate_func):
    """获取缓存的图表或生成新图表"""
    now = datetime.now()
    if key in _chart_cache:
        chart_data, timestamp = _chart_cache[key]
        if now - timestamp < _cache_timeout:
            return chart_data

    # 生成新图表
    chart_data = generate_func()
    _chart_cache[key] = (chart_data, now)
    return chart_data
@app.context_processor
def utility_processor():
    return dict(min=min, max=max, len=len)

# 初始化Spark处理器
spark_processor = SparkProcessor()

# 创建数据库表
create_tables(app)

@app.route('/')
def index():
    if 'username' in session:
        return redirect(url_for('dashboard'))
    return redirect(url_for('login'))


@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']

        # 验证输入
        if not username or not password:
            flash('用户名和密码不能为空')
            return render_template('login.html')

        try:
            # 查找用户
            user = User.query.filter_by(username=username).first()
            if user:
                # 验证密码
                if user.check_password(password):
                    if user.is_active:
                        session['username'] = user.username
                        session['user_id'] = user.id

                        # 更新最后登录时间
                        user.update_last_login()

                        flash(f'欢迎回来，{user.username}！')
                        return redirect(url_for('dashboard'))
                    else:
                        flash('账户已被禁用，请联系管理员')
                else:
                    flash('密码错误')
            else:
                # 自动注册新用户
                new_user = User(username=username, password=password)
                db.session.add(new_user)
                db.session.commit()

                session['username'] = new_user.username
                session['user_id'] = new_user.id

                flash('新用户已自动注册并登录')
                return redirect(url_for('dashboard'))

        except Exception as e:
            flash(f'登录失败：{str(e)}')
            app.logger.error(f'登录错误：{str(e)}')

    return render_template('login.html')


@app.route('/logout')
def logout():
    username = session.get('username')
    session.clear()
    if username:
        flash(f'{username}，您已成功退出登录')
    return redirect(url_for('login'))


@app.route('/register', methods=['GET', 'POST'])
def register():
    """用户注册页面（可选功能）"""
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        confirm_password = request.form['confirm_password']
        email = request.form.get('email', '')

        # 验证输入
        if not username or not password:
            flash('用户名和密码不能为空')
            return render_template('register.html')

        if password != confirm_password:
            flash('两次输入的密码不一致')
            return render_template('register.html')

        if len(password) < 6:
            flash('密码长度至少6位')
            return render_template('register.html')

        try:
            # 检查用户名是否已存在
            if User.query.filter_by(username=username).first():
                flash('用户名已存在')
                return render_template('register.html')

            # 创建新用户
            new_user = User(username=username, password=password, email=email)
            db.session.add(new_user)
            db.session.commit()

            flash('注册成功，请登录')
            return redirect(url_for('login'))

        except Exception as e:
            flash(f'注册失败：{str(e)}')
            app.logger.error(f'注册错误：{str(e)}')

    return render_template('register.html')


@app.route('/profile')
def profile():
    """用户个人信息页面"""
    if 'user_id' not in session:
        return redirect(url_for('login'))

    user = User.query.get(session['user_id'])
    if not user:
        session.clear()
        return redirect(url_for('login'))

    return render_template('profile.html', user=user)


@app.route('/update_profile', methods=['POST'])
def update_profile():
    """更新用户信息"""
    if 'user_id' not in session:
        return redirect(url_for('login'))

    user = User.query.get(session['user_id'])
    if not user:
        session.clear()
        return redirect(url_for('login'))

    email = request.form.get('email')
    current_password = request.form.get('current_password')
    new_password = request.form.get('new_password')

    try:
        # 更新邮箱
        if email:
            user.email = email

        # 更新密码
        if current_password and new_password:
            if user.check_password(current_password):
                user.set_password(new_password)
                flash('密码更新成功')
            else:
                flash('当前密码错误')
                return redirect(url_for('profile'))

        db.session.commit()
        flash('个人信息更新成功')

    except Exception as e:
        flash(f'更新失败：{str(e)}')
        app.logger.error(f'更新用户信息错误：{str(e)}')

    return redirect(url_for('profile'))


@app.route('/dashboard')
def dashboard():
    if 'username' not in session:
        return redirect(url_for('login'))

    # 获取用户信息
    user = User.query.get(session['user_id'])
    if not user:
        session.clear()
        return redirect(url_for('login'))

    return render_template('dashboard.html', username=session['username'], user=user)


@app.route('/page1')
def page1():
    if 'username' not in session:
        return redirect(url_for('login'))

    # 获取搜索参数
    city = request.args.get('city', '')
    industry = request.args.get('industry', '')
    salary_range = request.args.get('salary_range', '')

    # 分页参数
    page = request.args.get('page', 1, type=int)
    per_page = 10

    # 搜索职位
    all_positions = spark_processor.search_positions(city, industry, salary_range)

    # 计算分页
    total_positions = len(all_positions)
    total_pages = (total_positions + per_page - 1) // per_page

    # 确保页码在有效范围内
    page = max(1, min(page, total_pages))

    # 获取当前页的数据
    start_idx = (page - 1) * per_page
    end_idx = start_idx + per_page
    current_positions = all_positions.iloc[start_idx:end_idx]

    # 计算显示的记录范围
    start_display = start_idx + 1
    end_display = start_idx + len(current_positions)

    return render_template('page1.html',
                           positions=current_positions.to_dict('records'),
                           city=city,
                           industry=industry,
                           salary_range=salary_range,
                           page=page,
                           total_pages=total_pages,
                           total_positions=total_positions,
                           per_page=per_page,
                           start_display=start_display,
                           end_display=end_display)


@app.route('/page2')
def page2():
    if 'username' not in session:
        return redirect(url_for('login'))

    # 忽略所有警告
    with warnings.catch_warnings():
        warnings.simplefilter("ignore")

        # 箱线图数据
        city_salary_data = spark_processor.get_city_salary_data()

        # 生成箱线图
        plt.figure(figsize=(16, 10))
        if not city_salary_data.empty and 'median' in city_salary_data.columns:
            try:
                # 创建适合箱线图的数据格式
                boxplot_data = []
                labels = []

                # 按中位数排序
                city_salary_data = city_salary_data.sort_values('median', ascending=False)

                for _, row in city_salary_data.iterrows():
                    # 为每个城市创建模拟数据点（基于中位数和四分位数）
                    city_data = []
                    # 添加一些随机变化来模拟真实数据分布
                    np.random.seed(42)
                    median = row['median'] / 1000  # 转换为k单位
                    q1 = row.get('q1', median * 0.8) / 1000
                    q3 = row.get('q3', median * 1.2) / 1000

                    # 生成模拟数据点
                    n_points = 50
                    lower_data = np.random.normal(q1, max(0.1, (median - q1) * 0.2), n_points // 2)
                    upper_data = np.random.normal(q3, max(0.1, (q3 - median) * 0.2), n_points // 2)
                    city_data = np.concatenate([lower_data, upper_data])

                    boxplot_data.append(city_data)
                    labels.append(row['city'])

                # 使用matplotlib直接绘制箱线图
                box_plot = plt.boxplot(boxplot_data, labels=labels, patch_artist=True)

                # 美化箱线图
                colors = plt.cm.Set3(np.linspace(0, 1, len(boxplot_data)))
                for patch, color in zip(box_plot['boxes'], colors):
                    patch.set_facecolor(color)

                plt.title('各城市薪资分布箱线图（前25个城市）', fontsize=16, pad=20)
                plt.xticks(rotation=45, ha='right')
                plt.ylabel('薪资 (k)', fontsize=12)
                plt.grid(True, alpha=0.3)
                plt.tight_layout()

            except Exception as e:
                print(f"箱线图生成错误: {e}")
                plt.text(0.5, 0.5, '生成箱线图时出错', ha='center', va='center', transform=plt.gca().transAxes,
                         fontsize=12)
                plt.title('各城市薪资分布箱线图', fontsize=14)
        else:
            plt.text(0.5, 0.5, '暂无薪资数据', ha='center', va='center', transform=plt.gca().transAxes, fontsize=12)
            plt.title('各城市薪资分布箱线图', fontsize=14)

        img = io.BytesIO()
        plt.savefig(img, format='png', dpi=100, bbox_inches='tight')
        img.seek(0)
        boxplot_url = base64.b64encode(img.getvalue()).decode()
        plt.close()

        # 热力图数据
        heatmap_data = spark_processor.get_industry_city_salary()

        # 生成热力图
        plt.figure(figsize=(20, 12))
        if not heatmap_data.empty and 'avg_salary' in heatmap_data.columns:
            try:
                pivot_data = heatmap_data.pivot(index='industry', columns='city', values='avg_salary')

                # 确保数据有效
                if not pivot_data.empty:
                    # 对行业和城市进行排序（按平均值）
                    industry_means = pivot_data.mean(axis=1).sort_values(ascending=False)
                    city_means = pivot_data.mean(axis=0).sort_values(ascending=False)

                    pivot_data = pivot_data.loc[industry_means.index, city_means.index]

                    sns.heatmap(pivot_data,
                                annot=True,
                                fmt='.1f',
                                cmap='YlOrRd',
                                cbar_kws={'label': '平均薪资 (k)'},
                                linewidths=0.5,
                                annot_kws={'size': 8})
                    plt.title('行业-城市薪资热力图（前25个行业和城市）', fontsize=16, pad=20)
                    plt.tight_layout()
                else:
                    plt.text(0.5, 0.5, '数据不足以生成热力图', ha='center', va='center', transform=plt.gca().transAxes,
                             fontsize=12)
                    plt.title('行业-城市薪资热力图', fontsize=14)
            except Exception as e:
                print(f"热力图生成错误: {e}")
                plt.text(0.5, 0.5, '生成热力图时出错', ha='center', va='center', transform=plt.gca().transAxes,
                         fontsize=12)
                plt.title('行业-城市薪资热力图', fontsize=14)
        else:
            plt.text(0.5, 0.5, '暂无薪资数据', ha='center', va='center', transform=plt.gca().transAxes, fontsize=12)
            plt.title('行业-城市薪资热力图', fontsize=14)

        img2 = io.BytesIO()
        plt.savefig(img2, format='png', dpi=100, bbox_inches='tight')
        img2.seek(0)
        heatmap_url = base64.b64encode(img2.getvalue()).decode()
        plt.close()

    return render_template('page2.html',
                           boxplot_url=boxplot_url,
                           heatmap_url=heatmap_url)


@app.route('/page3')
def page3():
    if 'username' not in session:
        return redirect(url_for('login'))

    position_type = request.args.get('position_type', '数据分析')
    skills_text = spark_processor.get_skills_data(position_type)

    # 生成词云
    if skills_text and skills_text.strip():
        try:
            with warnings.catch_warnings():
                warnings.simplefilter("ignore")

                # 设置中文字体路径
                font_path = None
                # 尝试常见的中文字体路径
                possible_fonts = [
                    'C:/Windows/Fonts/simhei.ttf',  # Windows
                    'C:/Windows/Fonts/msyh.ttc',  # Windows 微软雅黑
                    '/System/Library/Fonts/Arial.ttf',  # macOS
                    '/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf',  # Linux
                ]

                for font in possible_fonts:
                    if os.path.exists(font):
                        font_path = font
                        break

                if font_path is None:
                    # 如果没有找到中文字体，使用默认字体
                    wordcloud = WordCloud(
                        width=800,
                        height=400,
                        background_color='white',
                        max_words=100,
                        collocations=False
                    ).generate(skills_text)
                else:
                    wordcloud = WordCloud(
                        font_path=font_path,
                        width=800,
                        height=400,
                        background_color='white',
                        max_words=100,
                        collocations=False
                    ).generate(skills_text)

                plt.figure(figsize=(10, 5))
                plt.imshow(wordcloud, interpolation='bilinear')
                plt.axis('off')
                plt.title(f'{position_type}技能要求词云', fontsize=14)

                img = io.BytesIO()
                plt.savefig(img, format='png', dpi=100, bbox_inches='tight')
                img.seek(0)
                wordcloud_url = base64.b64encode(img.getvalue()).decode()
                plt.close()
        except Exception as e:
            print(f"词云生成错误: {e}")
            wordcloud_url = None
    else:
        wordcloud_url = None

    return render_template('page3.html',
                           wordcloud_url=wordcloud_url,
                           position_type=position_type)


@app.route('/page4')
def page4():
    if 'username' not in session:
        return redirect(url_for('login'))

    with warnings.catch_warnings():
        warnings.simplefilter("ignore")

        # 学历分布饼图
        education_data = spark_processor.get_education_data()

        plt.figure(figsize=(10, 8))
        if not education_data.empty and 'count' in education_data.columns:
            try:
                # 过滤掉数量为0的学历
                filtered_data = education_data[education_data['count'] > 0]
                if not filtered_data.empty:
                    plt.pie(filtered_data['count'], labels=filtered_data['education'], autopct='%1.1f%%', startangle=90)
                    plt.title('学历分布', fontsize=14)
                else:
                    plt.text(0.5, 0.5, '暂无学历数据', ha='center', va='center', transform=plt.gca().transAxes,
                             fontsize=12)
                    plt.title('学历分布', fontsize=14)
            except Exception as e:
                print(f"饼图生成错误: {e}")
                plt.text(0.5, 0.5, '生成饼图时出错', ha='center', va='center', transform=plt.gca().transAxes,
                         fontsize=12)
                plt.title('学历分布', fontsize=14)
        else:
            plt.text(0.5, 0.5, '暂无学历数据', ha='center', va='center', transform=plt.gca().transAxes, fontsize=12)
            plt.title('学历分布', fontsize=14)

        img = io.BytesIO()
        plt.savefig(img, format='png', dpi=100, bbox_inches='tight')
        img.seek(0)
        piechart_url = base64.b64encode(img.getvalue()).decode()
        plt.close()

        # 行业学历堆叠柱状图
        industry_edu_data = spark_processor.get_industry_education_data()

        plt.figure(figsize=(14, 10))
        if not industry_edu_data.empty and 'count' in industry_edu_data.columns:
            try:
                pivot_data = industry_edu_data.pivot(index='industry', columns='education', values='count').fillna(0)
                if not pivot_data.empty:
                    # 按总数排序，只显示前15个行业
                    pivot_data['total'] = pivot_data.sum(axis=1)
                    pivot_data = pivot_data.sort_values('total', ascending=False).head(15).drop('total', axis=1)

                    # 选择主要的学历类型
                    main_educations = pivot_data.sum().sort_values(ascending=False).head(8).index
                    pivot_data = pivot_data[main_educations]

                    ax = pivot_data.plot(kind='bar', stacked=True, figsize=(14, 10),
                                         color=plt.cm.Set3(np.linspace(0, 1, len(main_educations))))
                    plt.title('各行业学历要求分布（前15个行业）', fontsize=16, pad=20)
                    plt.xticks(rotation=45, ha='right')
                    plt.ylabel('职位数量', fontsize=12)
                    plt.legend(title='学历', bbox_to_anchor=(1.05, 1), loc='upper left')
                    plt.grid(True, alpha=0.3, axis='y')
                    plt.tight_layout()
                else:
                    plt.text(0.5, 0.5, '数据不足以生成柱状图', ha='center', va='center', transform=plt.gca().transAxes,
                             fontsize=12)
                    plt.title('各行业学历要求分布', fontsize=14)
            except Exception as e:
                print(f"柱状图生成错误: {e}")
                plt.text(0.5, 0.5, '生成柱状图时出错', ha='center', va='center', transform=plt.gca().transAxes,
                         fontsize=12)
                plt.title('各行业学历要求分布', fontsize=14)
        else:
            plt.text(0.5, 0.5, '暂无行业学历数据', ha='center', va='center', transform=plt.gca().transAxes, fontsize=12)
            plt.title('各行业学历要求分布', fontsize=14)

        img2 = io.BytesIO()
        plt.savefig(img2, format='png', dpi=100, bbox_inches='tight')
        img2.seek(0)
        barchart_url = base64.b64encode(img2.getvalue()).decode()
        plt.close()

    return render_template('page4.html',
                           piechart_url=piechart_url,
                           barchart_url=barchart_url)


# 在 app.py 的 page5 路由中修改预测部分
@app.route('/page5', methods=['GET', 'POST'])
def page5():
    if 'username' not in session:
        return redirect(url_for('login'))

    prediction = None
    model_status = "已加载" if spark_processor.model_loaded else "未加载"
    model_path = config.MODEL_PATH

    if request.method == 'POST' or (
            request.method == 'GET' and any(param in request.args for param in ['city', 'industry', 'education'])):
        try:
            # 获取参数
            if request.method == 'POST':
                city = request.form.get('city', '北京')
                industry = request.form.get('industry', '互联网')
                education = request.form.get('education', '本科')
                experience = request.form.get('experience', '3-5年')
                company_size = request.form.get('company_size', '100-500人')
            else:
                city = request.args.get('city', '北京')
                industry = request.args.get('industry', '互联网')
                education = request.args.get('education', '本科')
                experience = request.args.get('experience', '3-5年')
                company_size = request.args.get('company_size', '100-500人')

            # 直接使用预训练模型进行预测
            prediction = spark_processor.predict_salary(city, industry, education, experience, company_size)

        except Exception as e:
            print(f"预测错误: {e}")
            prediction = "预测失败，请检查模型状态"

    return render_template('page5.html',
                           prediction=prediction,
                           model_status=model_status,
                           model_path=model_path)


if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=5000)